Skip to content

sync assessment with calc via context#3

Merged
reactlabs-dev merged 5 commits intomainfrom
feature/improve-print
Jan 28, 2026
Merged

sync assessment with calc via context#3
reactlabs-dev merged 5 commits intomainfrom
feature/improve-print

Conversation

@reactlabs-dev
Copy link
Owner

No description provided.

Copilot AI review requested due to automatic review settings January 27, 2026 23:50
@vercel
Copy link

vercel bot commented Jan 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
polydraiq Ready Ready Preview, Comment Jan 28, 2026 1:03am

Request Review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a React Context (AssessmentContext) to centralize and synchronize assessment state across the application, eliminating duplicate state management between the QuestionnaireDialog and CompositeScoreDisplay components.

Changes:

  • Created AssessmentContext for global assessment state management (results, completion status, and report generation)
  • Refactored QuestionnaireDialog to use context and simplified UI with new question card layout
  • Updated CompositeScoreDisplay to use context and added assessment reset functionality
  • Extracted assessment calculation and printing logic into a new assessmentPrint.ts utility file

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/app/context/AssessmentContext.tsx New context provider managing assessment results, completion state, and print functionality
src/app/components/questionnaire/assessmentPrint.ts New utility file with assessment calculation logic and HTML report generation
src/app/components/questionnaire/QuestionnaireDialog.tsx Integrated context, removed onAssessmentComplete prop, simplified UI with new question card design
src/app/components/compositeScoreDisplay/CompositeScoreDisplay.tsx Integrated context, removed duplicate state, added reset functionality with dialog remounting
src/app/components/calculator/Calculator.tsx Added reset handler using function initializer for proper state isolation
src/app/app.tsx Wrapped Routes with AssessmentProvider

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

size="small"
outlined
onClick={downloadReport}
disabled={!isAssessmentComplete}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistency: The downloadReport button is disabled based on isAssessmentComplete from context, but downloadReport() uses local assessmentResults state via generateReport(). This can cause runtime errors if the button becomes enabled (via context) but local state is empty after remounting. Use context's assessmentResults in generateReport() for consistency.

Suggested change
disabled={!isAssessmentComplete}
disabled={!isAssessmentComplete || !assessmentResults}

Copilot uses AI. Check for mistakes.
}

const totalScore = assessmentResults.reduce((sum, result) => sum + result.score, 0);
const maxTotalScore = assessmentResults.length * 25;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic issue: maxTotalScore is calculated as assessmentResults.length * 25, which assumes all sections are present. However, if only some sections are completed, this calculation will be incorrect. For partial assessments, this should sum the actual maxScore from each result: assessmentResults.reduce((sum, result) => sum + result.maxScore, 0).

Suggested change
const maxTotalScore = assessmentResults.length * 25;
const maxTotalScore = assessmentResults.reduce((sum, result) => sum + result.maxScore, 0);

Copilot uses AI. Check for mistakes.
label={tab === SECTIONS.length - 1 ? 'Finish Section' : 'Save & Next Section'}
icon="pi pi-arrow-right"
size="small"
onClick={handleCalculateScore}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation: The "Save & Next Section" button is no longer disabled when questions are unanswered. The previous implementation had a disabled attribute checking if all questions were answered. Without this validation, users can proceed without completing all questions in a section, which may lead to incomplete assessments. Consider adding back the validation: disabled={currentQuestions.some(q => answers[q.id] === undefined)}.

Suggested change
onClick={handleCalculateScore}
onClick={handleCalculateScore}
disabled={currentQuestions.some(q => answers[q.id] === undefined)}

Copilot uses AI. Check for mistakes.
Comment on lines 767 to 772
<Button
label={tab === SECTIONS.length - 1 ? 'Finish Section' : 'Save & Next Section'}
icon="pi pi-arrow-right"
size="small"
onClick={handleCalculateScore}
/>
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing navigation: The "Previous" button that allowed users to go back to previous sections has been removed. This impacts user experience by preventing users from reviewing or changing answers in previously completed sections. Consider restoring the Previous button navigation functionality.

Suggested change
<Button
label={tab === SECTIONS.length - 1 ? 'Finish Section' : 'Save & Next Section'}
icon="pi pi-arrow-right"
size="small"
onClick={handleCalculateScore}
/>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<Button
label="Previous Section"
icon="pi pi-arrow-left"
size="small"
outlined
disabled={tab === 0}
onClick={() => {
if (tab > 0) {
setTab(tab - 1);
}
}}
/>
<Button
label={tab === SECTIONS.length - 1 ? 'Finish Section' : 'Save & Next Section'}
icon="pi pi-arrow-right"
size="small"
onClick={handleCalculateScore}
/>
</div>

Copilot uses AI. Check for mistakes.
Comment on lines 738 to 773
{/* Actions */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingBottom: '24px',
marginBottom: isAssessmentComplete ? '32px' : '0',
marginLeft: '-24px',
marginRight: '-24px',
paddingLeft: '24px',
paddingRight: '24px',
backgroundColor: 'white',
position: 'relative',
zIndex: 1
}}>
<div style={{ color: '#6b7280', fontSize: '16px', fontWeight: '500' }}>
Calculated Score: <strong>{calculateSectionScore().toFixed(2)} / 25</strong>
</div>
<div style={{ display: 'flex', gap: '16px' }}>
{tab > 0 && (
<Button
label="Previous"
icon="pi pi-arrow-left"
outlined
onClick={() => setTab(tab - 1)}
/>
)}
<Button
label={tab === SECTIONS.length - 1 ? "Complete Assessment" : "Calculate & Next"}
icon="pi pi-check"
onClick={handleCalculateScore}
disabled={currentQuestions.some(q => answers[q.id] === undefined)}
/>
</div>
marginTop: '8px',
}}
>
<div style={{ display: 'flex', gap: '8px' }}>
<Button
label="Download Report (Markdown)"
icon="pi pi-download"
size="small"
outlined
onClick={downloadReport}
disabled={!isAssessmentComplete}
/>
<Button
label="Print Report"
icon="pi pi-print"
size="small"
onClick={handlePrintReport}
disabled={!isAssessmentComplete}
severity={isAssessmentComplete ? 'success' : 'secondary'}
outlined={!isAssessmentComplete}
/>
</div>

{/* Report Generation Section - Only show when assessment is complete */}
{isAssessmentComplete && (
<div style={{
borderTop: '2px solid #e5e7eb',
paddingTop: '24px',
background: '#f8fafc',
margin: '-24px -24px 0 -24px',
padding: '32px 24px 24px 24px',
borderRadius: '0 0 6px 6px'
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px'
}}>
<div>
<div style={{ color: '#059669', fontWeight: '600', fontSize: '18px' }}>
✅ Assessment Complete!
</div>
<div style={{ color: '#6b7280', fontSize: '16px', fontWeight: '500' }}>
Total Score: {assessmentResults.reduce((sum, result) => sum + result.score, 0).toFixed(1)} / 150
</div>
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<Button
label="Download Report"
icon="pi pi-download"
onClick={downloadReport}
className="p-button-success"
size="small"
tooltip="Download detailed markdown report"
/>
<Button
label="Print Report"
icon="pi pi-print"
onClick={printReport}
outlined
size="small"
tooltip="Print assessment results"
/>
</div>
</div>
</div>
)}

<Button
label={tab === SECTIONS.length - 1 ? 'Finish Section' : 'Save & Next Section'}
icon="pi pi-arrow-right"
size="small"
onClick={handleCalculateScore}
/>
</div>
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing score feedback: The calculated section score display that was previously shown in a sticky footer has been removed. Users can no longer see their calculated score for the current section before proceeding. This reduces transparency about how their answers translate to scores. Consider adding back the section score display to provide immediate feedback.

Copilot uses AI. Check for mistakes.
Comment on lines 447 to 440
const [assessmentResults, setAssessmentResults] = useState<AssessmentResult[]>([]);
const [isAssessmentComplete, setIsAssessmentComplete] = useState(false);
const { setAssessmentResults: setGlobalAssessmentResults, isAssessmentComplete, setAssessmentComplete } = useAssessmentContext();
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State synchronization issue: The component maintains local assessmentResults state while also updating the global context. This creates dual sources of truth. When the dialog is reset (via dialogKey in CompositeScoreDisplay), the local state resets but the component continues using the stale local state rather than syncing with the context. Consider using the context's assessmentResults directly instead of maintaining local state, or ensure the local state is synchronized when the component remounts.

Copilot uses AI. Check for mistakes.
Comment on lines +589 to +595
const handlePrintReport = () => {
if (!assessmentResults.length) {
window.alert('Please complete the guided assessment before printing a report.');
return;
}

printAssessmentReport(assessmentResults);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistency: The handlePrintReport function checks local assessmentResults.length but should use the global context assessmentResults instead. This check will fail to recognize completed assessments if the dialog was remounted (which happens when reset via dialogKey). Use the context's assessmentResults for consistency.

Suggested change
const handlePrintReport = () => {
if (!assessmentResults.length) {
window.alert('Please complete the guided assessment before printing a report.');
return;
}
printAssessmentReport(assessmentResults);
const { assessmentResults: contextAssessmentResults } = useAssessmentContext();
const handlePrintReport = () => {
if (!contextAssessmentResults.length) {
window.alert('Please complete the guided assessment before printing a report.');
return;
}
printAssessmentReport(contextAssessmentResults);

Copilot uses AI. Check for mistakes.
Comment on lines 716 to 732
{question.options.map((option, optionIdx) => (
<div
key={optionIdx}
style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}
>
<RadioButton
inputId={`${question.id}_${optionIdx}`}
name={question.id}
value={option.score}
onChange={() => handleAnswerChange(question.id, option.score, option.text)}
checked={answers[question.id] === option.score}
/>
<label htmlFor={`${question.id}_${optionIdx}`} style={{ fontSize: '14px', color: '#374151' }}>
{option.text}
</label>
</div>
</div>
))}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduced visual feedback: The option containers that previously had visual styling (background color and border changes when selected) have been removed. The new implementation only relies on the RadioButton component's visual state to indicate selection. This may reduce the clarity of which option is selected, especially for users who might miss the small radio button indicator. Consider adding back some visual distinction for selected options.

Copilot uses AI. Check for mistakes.

export interface CompositeScoreDisplayProps {
score: number;
onQuestionnaireScoreUpdate?: (sectionIndex: number, score: number) => void;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation: The new onAssessmentReset prop is not documented. Consider adding JSDoc comments to explain when this callback is invoked and what it should do, especially since it's optional and may not be obvious to consumers of this component.

Suggested change
onQuestionnaireScoreUpdate?: (sectionIndex: number, score: number) => void;
onQuestionnaireScoreUpdate?: (sectionIndex: number, score: number) => void;
/**
* Optional callback invoked after the assessment has been reset and the
* guided assessment dialog has been closed. Use this to perform any
* additional cleanup or UI updates when starting a new assessment.
*/

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 26
const setAssessmentResults = (results: AssessmentResult[]) => {
setAssessmentResultsState(results);
};
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary wrapper: The setAssessmentResults function is a simple wrapper around setAssessmentResultsState with no additional logic. Consider exposing setAssessmentResultsState directly in the context value as setAssessmentResults to reduce code complexity.

Copilot uses AI. Check for mistakes.
@reactlabs-dev reactlabs-dev merged commit 90bc895 into main Jan 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant